/**
 *  Copyright 2007-2008 University Of Southern California
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */


package edu.isi.pegasus.planner.partitioner;

import edu.isi.pegasus.planner.partitioner.graph.GraphNode;

import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;

/**
 * Does a topological sort on the Partition.
 *
 * @author Karan Vahi
 * @version $Revision$
 */

public class Topological //implements Iterator
{

    /**
     * The partition that has to be sorted.
     */
    private Partition mPartition;


    /**
     * An array that contains the  number of incoming edges to a node.
     */
    private int[] mInDegree;

    /**
     * A Map that returns the index into mInDegree map for a particular node
     * in graph. Maps a ID of the node to an int value, which is the index to
     * to the array containing the in degree for each node.
     *
     * @see #mInDegree
     */
    private Map mIndexMap;

    /**
     * The overloaded constructor.
     *
     * @param p   the partition that has to be sorted.
     */
    public Topological( Partition p ){
        mPartition = p;
        initialize();
    }

    /**
     * Initializes the inDegree for each node of the partition.
     *
     */
    public void initialize(){
        //build up a inDegree map for each node.
        int order = mPartition.size();
        mInDegree = new int[ order ];
        mIndexMap = new HashMap( order );

        int index = 0;
        //each of the root nodes have in degree of 0
        for ( Iterator it = mPartition.getRootNodes().iterator(); it.hasNext(); ){
            GraphNode root = (GraphNode)it.next();
            mIndexMap.put( root.getID(), new Integer( index ) );
            mInDegree[ index++ ] = 0;
        }

        //determine inDegree for other nodes
        for( Iterator it = mPartition.getRelations().entrySet().iterator(); it.hasNext(); ){
            Map.Entry entry = (Map.Entry) it.next();

            mIndexMap.put( entry.getKey(), new Integer( index) );
            mInDegree[ index++ ] = ((List) entry.getValue()).size();
        }

        //sanity check
        if( index != order){
            throw new RuntimeException( "Index does not match order of partition " + mPartition.getID());
        }

    }

    /**
     * Topologically sorts the partition and returns a List of
     * <code>GraphNode</code> elements. The iterator of the list, returns
     * the elements in the topological order.
     *
     * @return List of <code>GraphNode</code> objects
     */
    public List sort(){
        List l = new LinkedList( );
        int order = mPartition.size();

        //get all the adjaceny list representation
        Map relations = this.childrenRepresentation();

        List queue = new LinkedList();

        //add all the root nodes to queue first
        for( Iterator it = this.mPartition.getRootNodes().iterator(); it.hasNext(); ){
            queue.add( ((GraphNode)it.next()).getID() );
        }

        int index;
        while( !queue.isEmpty() ){
            String nodeID = (String)queue.remove(0);
            l.add( nodeID );

            //traverse all the children of the node
            if( relations.containsKey( nodeID) ){
                for( Iterator it = ( (List)relations.get( nodeID )).iterator(); it.hasNext();){
                    String childID = (String)it.next();
                    //remove the edge from node to child by decrementing inDegree
                    index = index(childID);
                    mInDegree[ index ] -= 1;

                    if( mInDegree[ index ] == 0 ){
                        //add the node to the queue
                        queue.add( childID );
                    }
                }
            }
        }

        //sanity check
        if( l.size() != order ){
            throw new RuntimeException( " Partition  " + mPartition.getID() + " has a cycle");
        }

        return l;
    }

    /**
     * Returns a map that is index by GraphNode ID's and each value is the list
     * of ID's of children of that GraphNode.
     *
     * @return Map that contains adjacency list's for each node.
     */
    protected Map childrenRepresentation(){
        //adjacency list where List contains parents
        Map m = new HashMap( mPartition.size() );

        for( Iterator it = mPartition.getRelations().entrySet().iterator(); it.hasNext();){
            Map.Entry entry = (Map.Entry)it.next();
            Object node  = entry.getKey();
            List parents = (List)entry.getValue();

            List children = null;
            for( Iterator pit = parents.iterator(); pit.hasNext(); ){
                Object parent = pit.next();
                //the node should be in parents adjacency list
                if( m.containsKey( parent )){
                    children = (List)m.get(parent);
                    children.add( node );
                }
                else{
                    children = new ArrayList(5);
                    children.add( node );
                    m.put( parent, children);
                }
            }
        }

        return m;
    }


    /**
     * Returns the index of a particular node. The index is used as an index into
     * arrays.
     *
     * @param id  the id of the node.
     *
     * @return the index
     */
    private int index( String id ){
        return ((Integer)mIndexMap.get( id )).intValue();
    }

}
